import { uniqBy, get, set, cloneDeep } from "lodash-es";
import type { FormProps, FormSchemaInner as FormSchema } from "../types/form";
import { isArray, isEmpty, isFunction, isNil, isObject } from "@/utils/is";
import { deepMerge } from "@/utils";
import { FormInstance } from "antd";
import { useEffect, useRef } from "react";
import { NamePath } from "antd/lib/form/interface";
import { dateItemType } from "../helper";
import moment from "moment";

interface UseFormActionContext {
	getProps: FormProps;
	getSchema: FormSchema[];
	formElRef: FormInstance;
	schemaRef: [FormSchema[] | null, Function];
	handleFormValues: Fn;
	setFormModel: Fn;
	// emit
	submit?: Function;
	reset?: Function;
	formModel: Recordable;
}

function tryConstructArray(field: string, values: Recordable = {}): any[] | undefined {
	const pattern = /^\[(.+)\]$/;
	if (pattern.test(field)) {
		const match = field.match(pattern);
		if (match && match[1]) {
			const keys = match[1].split(",");
			if (!keys.length) return undefined;

			const result: any[] = [];
			keys.forEach((k, index) => {
				set(result, index, values[k.trim()]);
			});

			return result.filter(Boolean).length ? result : undefined;
		}
	}
}

function tryConstructObject(field: string, values: Recordable = {}): Recordable | undefined {
	const pattern = /^\{(.+)\}$/;
	if (pattern.test(field)) {
		const match = field.match(pattern);
		if (match && match[1]) {
			const keys = match[1].split(",");
			if (!keys.length) return;

			const result = {};
			keys.forEach(k => {
				set(result, k.trim(), values[k.trim()]);
			});

			return Object.values(result).filter(Boolean).length ? result : undefined;
		}
	}
}

export function useFormEvents({
	getProps,
	getSchema,
	formElRef,
	schemaRef,
	handleFormValues,
	setFormModel,
	submit,
	reset,
	formModel
}: UseFormActionContext) {
	/**start-@updateBy:Jok-2024/12/20-18:11-由于Vue3和React差异,React中useMemo传递函数非响应式数据,所以useMemo都需要暂存最新值 */
	const getPropsRef = useRef<any>(getProps);
	const getSchemaRef = useRef<FormSchema[]>(getSchema);
	/**end-@updateBy:Jok-2024/12/20-18:11-由于Vue3和React差异,React中useMemo传递函数非响应式数据,所以useMemo都需要暂存最新值 */

	// watch change
	useEffect(() => {
		getSchemaRef.current = getSchema;
		getPropsRef.current = getProps;
		return () => {
			getSchemaRef.current = [];
			getPropsRef.current = null;
		};
	}, [getProps, getSchema]);

	// 获取表单fields
	const getAllFields = () => {
		return getSchemaRef.current
			.map((item: FormSchema) => [...(item.fields || []), item.field])
			.flat(1)
			.filter(Boolean);
	};

	function getFieldsValue(): Recordable {
		if (!formElRef) return {};
		return handleFormValues(formElRef.getFieldsValue());
	}

	/**
	 * @description: Set form value
	 */
	async function setFieldsValue(values: Recordable): Promise<void> {
		if (Object.keys(values).length === 0) return;

		/**@updateBy:Jok-2025/02/15-16:23-解决表单schema中隐藏的项无法设置值的问题 */
		let formStates: Recordable = {};

		const fields = getAllFields();

		const validKeys: string[] = [];
		fields.forEach((key: any) => {
			const schema = getSchemaRef.current.find(item => item.field === key);
			let value = get(values, key);
			const hasKey = Reflect.has(values, key);

			const { componentProps } = schema || {};
			let _props = componentProps as any;

			const constructValue = tryConstructArray(key, values) || tryConstructObject(key, values);
			// 0| '' is allow
			if (hasKey || !!constructValue) {
				const fieldValue = constructValue || value;
				// time type
				if (itemIsDateType(key)) {
					if (Array.isArray(fieldValue)) {
						const arr: any[] = [];
						for (const ele of fieldValue) arr.push(ele ? moment(ele) : null);
						formStates[key] = arr;
					} else {
						formStates[key] = fieldValue ? (_props?.valueFormat ? fieldValue : moment(fieldValue)) : null;
					}
				} else {
					formStates[key] = fieldValue;
				}
				// antd tips：change 事件仅当用户交互才会触发。该设计是为了防止在 change 事件中调用 setFieldsValue 导致的循环问题。
				// 好处：赋值后可以触发onChange控制其他组件。
				// 坏处：onChange中使用了setFieldsValue会导致循环问题。
				// 所以如果需要在onChange中动态控制表单，建议使用属性ifShow/show和dynamicDisabled。
				if (_props?.onChange && _props?.immediate) _props?.onChange(fieldValue, false);

				validKeys.push(key);
			}
		});
		formElRef?.setFieldsValue(formStates);
		setFormModel(formStates);
		validateFields(validKeys);
	}

	async function resetFields(nameList?: NamePath[]) {
		if (!formElRef) return;

		let _nameList: any;
		if (nameList === undefined) _nameList = getAllFields();
		let model = cloneDeep(formModel);
		_nameList.forEach((key: string) => {
			model[key] = undefined;
		});
		setFormModel(model);

		formElRef?.resetFields(nameList);
		reset && reset();
	}

	async function validateFields(nameList?: NamePath[]) {
		// let _nameList: any = nameList === undefined ? getAllFields():
		// if (nameList === undefined) _nameList = getAllFields();
		// else _nameList = nameList === Array.isArray(nameList) ? nameList : undefined;

		const values = await formElRef?.validateFields(nameList);
		return handleFormValues(values);
	}

	/**
	 * @description: Form submission
	 */
	async function handleSubmit(e?: Event): Promise<void> {
		e && e.preventDefault();
		const { submitFunc } = getPropsRef.current;
		if (submitFunc && isFunction(submitFunc)) {
			await submitFunc();
			return;
		}
		try {
			let values = await validateFields();
			submit && submit(values);
		} catch (error: any) {
			if (error?.outOfDate === false && error?.errorFields) return;
			throw new Error(error);
		}
	}

	async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
		let updateData: Partial<FormSchema>[] = [];
		if (isObject(data)) updateData.push(data as FormSchema);

		if (isArray(data)) updateData = [...data];

		const hasField = updateData.every(item => item.component === "Divider" || (Reflect.has(item, "field") && item.field));

		if (!hasField) {
			console.error("All children of the form Schema array that need to be updated must contain the `field` field");
			return;
		}
		getSchemaRef.current = updateData as FormSchema[];
		schemaRef[1](updateData as FormSchema[]);
	}

	async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
		let updateData: Partial<FormSchema>[] = [];
		if (isObject(data)) updateData.push(data as FormSchema);

		if (isArray(data)) updateData = [...data];

		const hasField = updateData.every(item => item.component === "Divider" || (Reflect.has(item, "field") && item.field));

		if (!hasField) {
			console.error("All children of the form Schema array that need to be updated must contain the `field` field");
			return;
		}
		const schema: FormSchema[] = [];
		getSchemaRef.current.forEach(val => {
			let _val;
			updateData.forEach(item => {
				if (val.field === item.field) _val = item;
			});
			if (_val !== undefined && val.field === _val.field) {
				const newSchema = deepMerge(val, _val);
				schema.push(newSchema as FormSchema);
			} else {
				schema.push(val);
			}
		});
		_setDefaultValue(schema);
		getSchemaRef.current = uniqBy(schema, "field");
		schemaRef[1](uniqBy(schema, "field"));
	}

	function _setDefaultValue(data: FormSchema | FormSchema[]) {
		let schemas: FormSchema[] = [];
		if (isObject(data)) schemas.push(data as FormSchema);

		if (isArray(data)) schemas = [...data];

		const obj: Recordable = {};
		const currentFieldsValue = formElRef?.getFieldsValue(true);
		schemas.forEach(item => {
			if (
				item.component !== "Divider" &&
				Reflect.has(item, "field") &&
				item.field &&
				!isNil(item.defaultValue) &&
				(!(item.field in currentFieldsValue) || isNil(currentFieldsValue[item.field]) || isEmpty(currentFieldsValue[item.field]))
			)
				obj[item.field] = item.defaultValue;
		});
		formElRef?.setFieldsValue(obj);
	}

	/**
	 * @description: Is it time
	 */
	function itemIsDateType(key: string) {
		return getSchemaRef.current.some(item => {
			return item.field === key && item.component ? dateItemType.includes(item.component) : false;
		});
	}

	return {
		getFieldsValue,
		setFieldsValue,
		resetFields,
		validateFields,
		handleSubmit,
		updateSchema,
		resetSchema
	};
}
